package org.kolonitsky.css;

import org.apache.batik.css.parser.*;
import org.apache.batik.css.parser.Parser;
import org.apache.batik.util.CSSConstants;
import org.apache.batik.util.ParsedURL;
import org.w3c.css.sac.*;

import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;

/**
 * @author Alex.Kolonitsky
 */
public class MyCssParser extends Parser{

    public MyCssParser() {
        setErrorHandler(MyCssErrorHandler.INSTANCE);
    }

    /**
     * Creates a scanner, given an InputSource.
     */
    protected Scanner createScanner(InputSource source) {
        documentURI = source.getURI();
        if (documentURI == null) {
            documentURI = "";
        }

        Reader r = source.getCharacterStream();
        if (r != null) {
            return new MyCssScanner(r);
        }

        InputStream is = source.getByteStream();
        if (is != null) {
            return new MyCssScanner(is, source.getEncoding());
        }

        String uri = source.getURI();
        if (uri == null) {
            throw new CSSException(formatMessage("empty.source", null));
        }

        try {
            ParsedURL purl = new ParsedURL(uri);
            is = purl.openStreamRaw(CSSConstants.CSS_MIME_TYPE);
            return new MyCssScanner(is, source.getEncoding());
        } catch (IOException e) {
            throw new CSSException(e);
        }
    }

    @Override
    protected void parseStyleDeclaration(boolean inSheet)
        throws CSSException {
        for (;;) {
            switch (current) {
                case LexicalUnits.EOF:
                    if (inSheet) {
                        throw createCSSParseException("eof");
                    }
                    return;
                case LexicalUnits.RIGHT_CURLY_BRACE:
                    if (!inSheet) {
                        throw createCSSParseException("eof.expected");
                    }
                    nextIgnoreSpaces();
                    return;
                case LexicalUnits.SEMI_COLON:
                    nextIgnoreSpaces();
                    continue;
                default:
                    throw createCSSParseException("identifier");
                case LexicalUnits.IDENTIFIER:
                case LexicalUnits.ANY:
            }

            String name = parsePropertyName();

            nextIgnoreSpaces();


            LexicalUnit exp = null;
            try {
                exp = parseExpression(false);
            } catch (CSSParseException e) {

                reportError(e);
            }

            if (exp != null) {
                boolean important = false;
                if (current == LexicalUnits.IMPORTANT_SYMBOL) {
                    important = true;
                    nextIgnoreSpaces();
                }
                documentHandler.property(name, exp, important);
            }
        }
    }

    private String parsePropertyName() {
        try {
            StringBuilder builder = new StringBuilder(scanner.getStringValue());
            loop: for (;;) {
                current = scanner.next();
                switch (current) {
                    case LexicalUnits.COMMENT:
                        documentHandler.comment(scanner.getStringValue());
                        break;
                    case LexicalUnits.COLON:
                    case LexicalUnits.SEMI_COLON:
                    case LexicalUnits.RIGHT_CURLY_BRACE:
                    case LexicalUnits.EOF:
                        break loop;

                    default:
                        builder.append(scanner.getStringValue());
                }
            }
            return builder.toString();
        } catch (ParseException e) {
            errorHandler.error(createCSSParseException(e.getMessage()));
            return "";
        }
    }

    /**
     * Parses a simple selector.
     */
    @Override
    protected SimpleSelector parseSimpleSelector() {
        SimpleSelector result;

        switch (current) {
        case LexicalUnits.IDENTIFIER:
            result = selectorFactory.createElementSelector
                (null, scanner.getStringValue());
            next();
            break;
        case LexicalUnits.ANY:
            next();
        default:
            result = selectorFactory.createElementSelector(null, null);
        }
        Condition cond = null;
        loop: for (;;) {
            Condition c = null;
            switch (current) {
            case LexicalUnits.HASH:
                c = conditionFactory.createIdCondition
                    (scanner.getStringValue());
                next();
                break;
            case LexicalUnits.DIMENSION:
                c = conditionFactory.createClassCondition(null, scanner.getStringValue().substring(1));
                next();
                break;
            case LexicalUnits.DOT:
                if (next() != LexicalUnits.IDENTIFIER) {
                    throw createCSSParseException("identifier");
                }
                c = conditionFactory.createClassCondition
                    (null, scanner.getStringValue());
                next();
                break;
            case LexicalUnits.LEFT_BRACKET:
                if (nextIgnoreSpaces() != LexicalUnits.IDENTIFIER) {
                    throw createCSSParseException("identifier");
                }
                String name = scanner.getStringValue();
                int op = nextIgnoreSpaces();
                switch (op) {
                default:
                    throw createCSSParseException("right.bracket");
                case LexicalUnits.RIGHT_BRACKET:
                    nextIgnoreSpaces();
                    c = conditionFactory.createAttributeCondition
                        (name, null, false, null);
                    break;
                case LexicalUnits.EQUAL:
                case LexicalUnits.INCLUDES:
                case LexicalUnits.DASHMATCH:
                    String val = null;
                    switch (nextIgnoreSpaces()) {
                    default:
                        throw createCSSParseException("identifier.or.string");
                    case LexicalUnits.STRING:
                    case LexicalUnits.IDENTIFIER:
                        val = scanner.getStringValue();
                        nextIgnoreSpaces();
                    }
                    if (current != LexicalUnits.RIGHT_BRACKET) {
                        throw createCSSParseException("right.bracket");
                    }
                    next();
                    switch (op) {
                    case LexicalUnits.EQUAL:
                        c = conditionFactory.createAttributeCondition
                            (name, null, false, val);
                        break;
                    case LexicalUnits.INCLUDES:
                        c = conditionFactory.createOneOfAttributeCondition
                            (name, null, false, val);
                        break;
                    default:
                        c = conditionFactory.
                            createBeginHyphenAttributeCondition
                            (name, null, false, val);
                    }
                }
                break;
            case LexicalUnits.COLON:
                switch (nextIgnoreSpaces()) {
                case LexicalUnits.IDENTIFIER:
                    String val = scanner.getStringValue();
                    if (isPseudoElement(val)) {
                        if (pseudoElement != null) {
                            throw createCSSParseException
                                ("duplicate.pseudo.element");
                        }
                        pseudoElement = val;
                    } else {
                        c = conditionFactory.createPseudoClassCondition
                            (null, val);
                    }
                    next();
                    break;
                case LexicalUnits.FUNCTION:
                    String func = scanner.getStringValue();
                    if (nextIgnoreSpaces() != LexicalUnits.IDENTIFIER) {
                        throw createCSSParseException("identifier");
                    }
                    String lang = scanner.getStringValue();
                    if (nextIgnoreSpaces() != LexicalUnits.RIGHT_BRACE) {
                        throw createCSSParseException("right.brace");
                    }

                    if (!func.equalsIgnoreCase("lang")) {
                        throw createCSSParseException("pseudo.function");
                    }

                    c = conditionFactory.createLangCondition(lang);

                    next();
                    break;
                default:
                    throw createCSSParseException("identifier");
                }
                break;
            default:
                break loop;
            }
            if (c != null) {
                if (cond == null) {
                    cond = c;
                } else {
                    cond = conditionFactory.createAndCondition(cond, c);
                }
            }
        }
        skipSpaces();
        if (cond != null) {
            result = selectorFactory.createConditionalSelector(result, cond);
        }
        return result;
    }

}
